﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint.Administration.Claims;
using Microsoft.Office.Server.UserProfiles;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
using System.Diagnostics;
using Microsoft.SharePoint.Taxonomy;
using Microsoft.SharePoint.Administration;
using System.Web;

namespace MMCP
{
    public class MetaMembershipClaimProvider : SPClaimProvider
    {
        private readonly char[] Delimiters = new char[] { '.', ',', ';' };
        private static readonly object _lock = new object();
        private Dictionary<SPClaim, IEnumerable<SPClaim>> _cache = new Dictionary<SPClaim, IEnumerable<SPClaim>>();

        public MetaMembershipClaimProvider(string displayName) : base(displayName)
        {
        }

        #region Profider Settings

        public override bool SupportsEntityInformation
        {
            get { return true; }
        }

        public override bool SupportsHierarchy
        {
            get { return true; }
        }

        public override bool SupportsResolve
        {
            get { return true; }
        }

        public override bool SupportsSearch
        {
            get { return true; }
        }

        internal static string ProviderDisplayName
        {
            get { return "Meta Membership"; }
        }

        internal static string ProviderInternalName
        {
            get { return "MMCP"; }
        }

        internal static string EntityGroupName
        {
            get { return ProviderInternalName; }
        }

        public override string Name
        {
            get { return ProviderInternalName; }
        }

        private static string ClaimType
        {
            get
            {
                return "http://schema.company.com/mmcp";
            }
        }

        private static string SAPClaimValueType
        {
            get
            {
                return Microsoft.IdentityModel.Claims.ClaimValueTypes.String;
            }
        }

        #endregion Profider Settings

        private IEnumerable<SPClaim> TryGetClaims(Uri context, SPClaim claim)
        {
            IEnumerable<SPClaim> claims;
            lock (_lock)
            {
                if (_cache.ContainsKey(claim))
                    claims = _cache[claim];
                else
                {
                    claims = InitializeClaims(context, claim);
                    _cache.Add(claim, claims);
                }
            }
            return claims;
        }

        private IEnumerable<SPClaim> InitializeClaims(Uri context, SPClaim entity)
        {
            #region Check and Get User object
            //figure out who the user is so we know what team to add to their claim
            //the entity.Value from the input parameter contains the name of the 
            //authenticated user.  for a SQL FBA user, it looks something like
            // 0#.f|sqlmembership|user1; for a Windows claims user it looks something
            //like 0#.w|steve\\wilmaf
            //I'll skip some boring code here to look at that name and figure out
            //if it's an FBA user or Windows user, and if it's an FBA user figure
            //out what the number part of the name is after "user"

            var account= SPClaimProviderManager.DecodeUserIdentifierClaim(entity);
            Debug.WriteLine("SPClaimProviderManager.DecodeUserIdentifierClaim = " + account);
            if (account == null)
                yield break;

            string loginName = account.Value;
            Debug.WriteLine("Value = " + loginName);
            if (string.IsNullOrEmpty(loginName))
                yield break;
            #endregion

            var ProfileManager = GetProfileManager(context);

            if(ProfileManager == null || string.IsNullOrEmpty(loginName))
                yield break;

            // if there is no profile created for the user return
            if (!ProfileManager.UserExists(loginName))
                yield break;

            UserProfile currentUserProfile = ProfileManager.GetUserProfile(loginName);
            if (currentUserProfile == null)
                yield break;

            IEnumerable<Term> userTerms = currentUserProfile.GetMetaMembershipTerms();
            if(userTerms ==null)
                yield break;

            foreach (var term in userTerms)
                yield return CreateClaim(ClaimType, term.Id.ToString("N"), SAPClaimValueType);
        }

        private UserProfileManager GetProfileManager(Uri context)
        {
            UserProfileManager upm = null;
            Debug.WriteLine("Gettig user profiled for context uri: " + context.ToString());

            SPSecurity.RunWithElevatedPrivileges(delegate()
            {
                try
                {
                    var adminapp = SPAdministrationWebApplication.Local;
                    var appuri = adminapp.GetResponseUri(SPUrlZone.Default);
                    using (SPSite site = new SPSite(appuri.ToString()))
                    {
                        var serviceContext = SPServiceContext.GetContext(site);
                        upm = new UserProfileManager(serviceContext, true);
                    }
                }
                catch (Exception exc)
                {
                }
            });

            Debug.WriteLine("Gettig user profiled for context uri: " + context.ToString() + " Result:" + upm);
            return upm;

            SPSecurity.RunWithElevatedPrivileges(delegate()
            {
                using (SPSite site = new SPSite(context.ToString()))
                using (SPWeb web = site.OpenWeb())
                {
                    var serviceContext = SPServiceContext.GetContext(site);
                    try
                    {
                        upm = new UserProfileManager(serviceContext, true);
                    }
                    catch (UserProfileApplicationNotAvailableException exc)
                    {
                    }
                    finally
                    {
                    }
                }
            });
            Debug.WriteLine("Gettig user profiled for context uri: " + context.ToString() + " Result:" + upm);
            return upm;
        }

        protected override void FillClaimsForEntity(Uri context, SPClaim entity, List<SPClaim> claims)
        {
            if (entity == null)
                throw new ArgumentNullException("entity");

            if (claims == null)
                throw new ArgumentNullException("claims");

            claims.AddRange(TryGetClaims(context, entity));
        }

        protected override void FillClaimTypes(List<string> claimTypes)
        {
            if (claimTypes == null)
                throw new ArgumentNullException("claimTypes");

            //add our claim type
            claimTypes.Add(ClaimType);
        }

        protected override void FillClaimValueTypes(List<string> claimValueTypes)
        {
            if (claimValueTypes == null)
                throw new ArgumentNullException("claimValueTypes");

            //add our claim value type
            claimValueTypes.Add(SAPClaimValueType);
        }


        private void CreateRootNodes(SPProviderHierarchyTree hierarchy, IEnumerable<TermHierarchyData> paths)
        {
            var rootNodes = paths.GetRootTerms();
            foreach (var nodePath in rootNodes)
            {
                SPProviderHierarchyNode node = new SPProviderHierarchyNode(MetaMembershipClaimProvider.ProviderDisplayName, nodePath.DisplayName, nodePath.HierarchyID, nodePath.HasChilds);
                hierarchy.AddChild(node);
            }
        }
        private void CreateParentedNodes(SPProviderHierarchyTree parentNode, IEnumerable<TermHierarchyData> paths, string parentNodeId)
        {
            IEnumerable<TermHierarchyData> filteredodes = paths.FilterTerms(parentNodeId);

            var list = from node in filteredodes
                       select new
                       {
                           HierarchyID = node.HierarchyID,
                           ShortName = node.DisplayName,
                           Origin = node
                       };

            foreach (var hn in filteredodes)
            {
                SPProviderHierarchyNode node = new SPProviderHierarchyNode(MetaMembershipClaimProvider.ProviderDisplayName,
                    hn.DisplayName,
                    hn.HierarchyID,
                    hn.HasChilds);
                if(hn.HasChilds)
                    parentNode.AddChild(node);

                parentNode.AddEntity(GetPickerEntity(hn));
            }
        }

        private IEnumerable<TermHierarchyData> GetAggregatedTerms(Uri context)
        {
            //UserProfileManager.GetStringSinglePropertyValueForUser
            var ProfileManager = GetProfileManager(context);

            var social = ProfileManager.GetSocialTerms().Distinct(new TermHierarchyDataComparer());

            return social.OrderBy(p => p.HierarchyID);
        }

        private void InitializeHierarchy(Uri context, SPProviderHierarchyTree hierarchy, string currentNodeID)
        {
            hierarchy.EntityData.Clear();

            var AllTerms = GetAggregatedTerms(context);

            // Check to see whether the hierarchyNodeID is null; it is when the control 
            // is first loaded, but if a user clicks on one of the nodes, it will return
            // the key of the node that was clicked. This lets you build out a 
            // hierarchy as a user clicks something, instead of all at once. So I 
            // wrote the code for that scenario, but I am not using it in that way.  
            // Which means that I could have just as easily used an 
            // if (hierarchyNodeID == null) in this particular implementation, instead
            // of a switch statement.
            if (string.IsNullOrEmpty(currentNodeID))
            {
                CreateRootNodes(hierarchy, AllTerms);
            }
            else
            {
                CreateParentedNodes(hierarchy, AllTerms, currentNodeID);
            }

        }

        protected override void FillHierarchy(Uri context, string[] entityTypes, string hierarchyNodeID, int numberOfLevels, Microsoft.SharePoint.WebControls.SPProviderHierarchyTree hierarchy)
        {
            // Ensure that People Picker is asking for the type of entity that we 
            // return; site collection administrator will not return, for example.
            if (!EntityTypesContain(entityTypes, SPClaimEntityTypes.FormsRole))
                return;

            InitializeHierarchy(context, hierarchy, hierarchyNodeID);
        }

        protected override void FillResolve(Uri context, string[] entityTypes, SPClaim resolveInput, List<Microsoft.SharePoint.WebControls.PickerEntity> resolved)
        {
            FillResolveInternal(context, entityTypes, resolveInput.Value, resolved);
        }

        protected override void FillResolve(Uri context, string[] entityTypes, string resolveInput, List<PickerEntity> resolved)
        {
            FillResolveInternal(context, entityTypes, resolveInput, resolved);
        }

        private void FillResolveInternal(Uri context, string[] entityTypes, string resolveInput, List<PickerEntity> resolved)
        {
            // Ensure that People Picker is asking for the type of entity that we 
            // return; site collection administrator will not return, for example.
            if (!EntityTypesContain(entityTypes, SPClaimEntityTypes.FormsRole))
                return;

            string invarInput = resolveInput.ToLowerInvariant();

            var AllTerms = GetAggregatedTerms(context);
            var result = from term in AllTerms
                         let hidinvar = term.HierarchyID.ToLowerInvariant()
                         let dnInvar = term.DisplayName.ToLowerInvariant()
                         let dnPath = term.Path.ToLowerInvariant()
                         where invarInput.Equals(hidinvar) || invarInput.Equals(dnInvar) || invarInput.Equals(dnPath)
                         select GetPickerEntity(term);

            resolved.AddRange(result);
        }

        protected override void FillSchema(SPProviderSchema schema)
        {
            // Add the schema element that we need at a minimum in our picker node.
            schema.AddSchemaElement(new SPSchemaElement(PeopleEditorEntityDataKeys.DisplayName, "Display Name", SPSchemaElementType.Both));
        }

        protected override void FillEntityTypes(List<string> entityTypes)
        {
            // Return the type of entity claim that we are using (only one in this case).
            entityTypes.Add(SPClaimEntityTypes.FormsRole);
        }

        protected override void FillSearch(Uri context, string[] entityTypes,
              string searchPattern, string hierarchyNodeID, int maxCount,
              Microsoft.SharePoint.WebControls.SPProviderHierarchyTree searchTree)
        {
            // Ensure that People Picker is asking for the type of entity that we 
            // return; site collection administrator will not return, for example.
            if (!EntityTypesContain(entityTypes, SPClaimEntityTypes.FormsRole))
                return;


            string invarPattern = searchPattern.ToLowerInvariant();

            var AllTerms = GetAggregatedTerms(context);
            
            var foundOut = from term in AllTerms
                           let hidinvar = term.HierarchyID.ToLowerInvariant()
                           let dnInvar = term.DisplayName.ToLowerInvariant()
                           let dnPath = term.Path.ToLowerInvariant()
                           where invarPattern.Contains(hidinvar) || invarPattern.Contains(dnInvar) || invarPattern.Contains(dnPath)
                           select GetPickerEntity(term);
            searchTree.AddEntities(foundOut);
        }

        private PickerEntity GetPickerEntity(TermHierarchyData ClaimValue)
        {
            // Use the helper function!
            PickerEntity pe = CreatePickerEntity();

            // Set the claim that is associated with this match.
            pe.Claim = CreateClaim(ClaimType, ClaimValue.HierarchyID, SAPClaimValueType);

            // Set the tooltip that is displayed when you pause over the resolved claim.
            pe.Description = MetaMembershipClaimProvider.ProviderDisplayName + " : " + ClaimValue.Path;

            // Set the text that we will display.
            //pe.DisplayText = ClaimValue.DisplayName;
            pe.DisplayText = ClaimValue.Path;

            // Store it here, in the hashtable **
            pe.EntityData[PeopleEditorEntityDataKeys.DisplayName] = ClaimValue.DisplayName;

            // We plug this in as a role type entity.
            pe.EntityType = SPClaimEntityTypes.FormsRole;

            // Flag the entry as being resolved.
            pe.IsResolved = true;

            // This is the first part of the description that shows
            // above the matches, like Role: Forms Auth when
            // you do an forms-based authentication search and find a matching role.
            pe.EntityGroupName = EntityGroupName;

            return pe;
        }
    }
}
